﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using JetBrains.Annotations;
using System.Linq;

namespace WikiDoc.Utils
{
    /// <summary>
    /// 对命令行参数进行说明
    /// </summary>
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
    public class CmdArgumentAttribute : Attribute
    {
        /// <summary>
        /// 参数名称，如果省略则取属性名称或字段名称为参数名，参数名需要用两个减号 -- 指明
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 参数是否是必须指定的，默认为 false
        /// </summary>
        public bool Required { get; set; }
        
        /// <summary>
        /// 参数别名，通常是较短的名称，默认为 null，没有别名，别名用一个减号 - 指明
        /// </summary>
        public string Alias { get; set; }

        /// <summary>
        /// 申明这是一个无名参数，bool 型的参数不能是匿名的，
        /// 如果申明了多个无名参数，则按照他们在属性列表中出现的先后次序
        /// </summary>
        public bool UnNamed { get; set; }
    }


    /// <summary>
    /// 对命令进行解析的工具类
    /// </summary>
    public static class CommandLineArgumentParser
    {
        /// <summary>
        /// 一个命令行参数的说明信息
        /// </summary>
        private class ArgumentDescriptor
        {
            /// <summary>
            /// Initializes a new instance of the ArgumentDescriptor class.
            /// </summary>
            /// <param name="obj">The obj.</param>
            /// <param name="member">The member.</param>
            /// <param name="attribute">The attribute.</param>
            public ArgumentDescriptor([NotNull] object obj, [NotNull] MemberInfo member, [NotNull] CmdArgumentAttribute attribute)
            {
                if (obj == null) throw new ArgumentNullException("obj");
                if (member == null || (member as PropertyInfo == null && member as FieldInfo == null))
                    throw new ArgumentNullException("member");
                if (attribute == null) throw new ArgumentNullException("attribute");

                _obj = obj;
                _field = member as FieldInfo;
                _property = member as PropertyInfo;
                _attribute = attribute;
            }

            private readonly FieldInfo _field;
            private readonly PropertyInfo _property;

            [NotNull]
            private readonly CmdArgumentAttribute _attribute;

            [NotNull]
            private readonly object _obj;

            public bool Setted { get; private set; }

            public void SetValue(object value)
            {
                if (_field != null)
                {
                    value = Convert.ChangeType(value, _field.FieldType);
                    _field.SetValue(_obj, value);
                }
                else if (_property != null)
                {
                    value = Convert.ChangeType(value, _property.PropertyType);
                    _property.SetValue(_obj, value, null);
                }

                Setted = true;
            }

            /// <summary>
            /// 参数完整名称，当使用两个减号时，应匹配这个名称
            /// </summary>
            [NotNull]
            public string Name
            {
                get { return _attribute.Name ?? (_field as MemberInfo ?? _property).Name; }
            }

            /// <summary>
            /// 别名，通常是用一个字符表示的简短的名称，当使用一个减号时，应匹配这个名称
            /// </summary>
            [CanBeNull]
            public string Alias
            {
                get { return _attribute.Alias; }
            }

            /// <summary>
            /// 是否是一个开关参数
            /// </summary>
            public bool IsSwitch
            {
                get
                {
                    var type = _property == null ? _field.FieldType : _property.PropertyType;

                    return type == typeof(bool);
                }
            }

            /// <summary>
            /// 是否是必须的参数
            /// </summary>
            public bool Required
            {
                get { return _attribute.Required && !IsSwitch; }
            }

            /// <summary>
            /// 是否是匿名参数
            /// </summary>
            public bool IsUnnamed
            {
                get { return _attribute.UnNamed && !IsSwitch; }
            }
        }

        [NotNull]
        private static List<ArgumentDescriptor> GetDescriptors([NotNull] object obj)
        {
            if (obj == null) throw new ArgumentNullException("obj");

            var list = new List<ArgumentDescriptor>();

            foreach (var member in obj.GetType().GetMembers())
            {
                var attr = member.GetCustomAttributes(typeof(CmdArgumentAttribute), true);
                if (attr.Length > 0)
                {
                    list.Add(new ArgumentDescriptor(obj, member, (CmdArgumentAttribute)attr[0]));
                }
            }

            return list;
        }

        /// <summary>
        /// 解析给定的命令行参数，保存解析结果，如果解析过程发生错误，则抛出 AugumentException 异常
        /// </summary>
        /// <param name="args">命令行参数</param>
        /// <param name="argObject">解析结果，不能为空</param>
        /// <exception cref="ArgumentException">如果给定的命令行参数不符合规范</exception>
        public static void ParseArguments([CanBeNull] string[] args, [NotNull] object argObject)
        {
            if (argObject == null) throw new ArgumentNullException("argObject");

            ParseArguments(args, GetDescriptors(argObject));
        }

        private static void ParseArguments([CanBeNull] string[] args, [NotNull] List<ArgumentDescriptor> argDescs)
        {
            if (argDescs == null) throw new ArgumentNullException("argDescs");

            var i = 0;

            var unnamedArgs = argDescs.FindAll(item => item.IsUnnamed);

            while (args != null && i < args.Length && i < unnamedArgs.Count)
            {
                var arg = args[i];

                if (arg[0] == '-') break;

                unnamedArgs[i].SetValue(arg);

                i++;
            }

            while (args != null && i < args.Length)
            {
                var arg = args[i++];

                if (arg.Length < 2) throw new ArgumentException(string.Format("无效的参数名称 {0}", arg));

                if (arg[0] != '-') throw new ArgumentException(string.Format("无效的参数名称 {0}", arg));

                ArgumentDescriptor desc = null;

                if (arg[1] == '-')
                {
                    var argName = arg.Substring(2);

                    desc = argDescs.FirstOrDefault(
                        item => string.Equals(argName, item.Name, StringComparison.OrdinalIgnoreCase));
                }
                else
                {
                    if (arg.Length == 2)
                    {
                        var alias = arg.Substring(1);
                        desc = argDescs.FirstOrDefault(
                            item => string.Equals(alias, item.Alias, StringComparison.Ordinal));
                    }
                }

                if (desc == null) throw new ArgumentException(string.Format("无效的参数名称 {0}", arg));

                if (desc.IsSwitch)
                {
                    desc.SetValue(true);
                }
                else
                {
                    if (i >= args.Length) throw new ArgumentException(string.Format("未指定参数 {0} 的值", desc.Name));

                    var argValue = args[i++];

                    desc.SetValue(argValue);
                }

            } // while

            foreach (var item in argDescs)
            {
                if (item.Required && !item.Setted)
                    throw new ArgumentException(string.Format("未指定必须的参数 {0}", item.Name));
            }
        }
    }


}
